package jp.co.cyberagent.android.gpuvideo.util;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.media.ImageReader;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.util.Size;
import android.view.Surface;

import androidx.annotation.RequiresApi;

import java.util.ArrayList;

/**
 * @author wenjie
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class Camera2Loader extends CameraLoader {

    private static final String TAG = "Camera2Loader";

    private static final int PREVIEW_WIDTH = 480;
    private static final int PREVIEW_HEIGHT = 640;
    private static final int ROTATION_90 = 90;
    private static final int ROTATION_270 = 270;

    private final Activity activity;

    private CameraDevice cameraInstance = null;
    private CameraCaptureSession captureSession = null;
//    private ImageReader imageReader = null;
    private int cameraFacing = CameraCharacteristics.LENS_FACING_BACK;
    private int viewWidth;
    private int viewHeight;
    private Surface surface;

    private final Handler handler = new Handler(Looper.myLooper());

    private final CameraManager cameraManager;

    public Camera2Loader(Activity activity) {
        this.activity = activity;
        cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
    }


    @Override
    public void onResume(int width, int height) {
        viewWidth = width;
        viewHeight = height;
        setUpCamera();
    }

    @Override
    public void onPause() {
        releaseCamera();
    }

    @Override
    public void switchCamera() {
        switch (cameraFacing) {
            case CameraCharacteristics.LENS_FACING_BACK:
                cameraFacing = CameraCharacteristics.LENS_FACING_FRONT;
                break;
            case CameraCharacteristics.LENS_FACING_FRONT:
                cameraFacing = CameraCharacteristics.LENS_FACING_BACK;
                break;
            default:
                return;
        }
        releaseCamera();
        setUpCamera();
    }

    @Override
    public int getCameraOrientation() {
        int degrees = 0;
        switch (activity.getWindowManager().getDefaultDisplay().getRotation()) {
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
            case Surface.ROTATION_0:
            default:
                break;
        }
        String cameraId = getCameraId(cameraFacing);
        if (cameraId == null) {
            return 0;
        }
        CameraCharacteristics characteristics = null;
        try {
            characteristics = cameraManager.getCameraCharacteristics(cameraId);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        Integer orientation = null;
        if (characteristics != null) {
            orientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        }
        if (orientation == null) {
            return 0;
        }
        if (cameraFacing == CameraCharacteristics.LENS_FACING_FRONT) {
            return (orientation + degrees) % 360;
        } else { // back-facing
            return (orientation - degrees) % 360;
        }
    }

    @Override
    public boolean hasMultipleCamera() {
        try {
            return cameraManager.getCameraIdList().length > 1;
        } catch (CameraAccessException e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public void setUpSurface(Surface surface) {
        this.surface = surface;
        handler.post(() -> {
            releaseCamera();
            setUpCamera();
        });
    }

    @SuppressLint("MissingPermission")
    private void setUpCamera() {
        String cameraId = getCameraId(cameraFacing);
        if (cameraId == null) {
            return;
        }
        try {
            cameraManager.openCamera(cameraId, new CameraDeviceCallback(), handler);
        } catch (CameraAccessException e) {
            Log.e(TAG, "Opening camera (ID: $cameraId) failed.");
        }
    }

    private void releaseCamera() {
//        if (imageReader != null) {
//            imageReader.close();
//        }
        if (cameraInstance != null) {
            cameraInstance.close();
        }
        if (captureSession != null) {
            captureSession.close();
        }
//        imageReader = null;
        cameraInstance = null;
        captureSession = null;
    }

    private String getCameraId(int facing) {
        try {
            String[] ids = cameraManager.getCameraIdList();
            for (String id : ids) {
                if (cameraManager.getCameraCharacteristics(id)
                        .get(CameraCharacteristics.LENS_FACING) == facing) {
                    return id;
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "getCameraId: ", e);
        }
        return null;
    }


    private void startCaptureSession() {
//        Size size = chooseOptimalSize();
//        imageReader =
//                ImageReader.newInstance(size.getWidth(), size.getHeight(), ImageFormat.YUV_420_888, 2);
        try {
            ArrayList<Surface> array = new ArrayList<>();
            array.add(surface);
//            array.add(imageReader.getSurface());
            cameraInstance.createCaptureSession(array, new CaptureStateCallback(), handler);
        } catch (CameraAccessException e) {
            Log.e(TAG, "Failed to start camera session", e);
        }
    }

    private Size chooseOptimalSize() {
        if (viewWidth == 0 || viewHeight == 0) {
            return new Size(0, 0);
        }
        String cameraId = getCameraId(cameraFacing);
        if (cameraId == null) {
            return new Size(0, 0);
        }
        Size[] outputSizes = null;
        try {
            outputSizes = cameraManager.getCameraCharacteristics(cameraId)
                    .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(ImageFormat.YUV_420_888);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        int orientation = getCameraOrientation();
        int maxPreviewWidth;
        int maxPreviewHeight;
        if (orientation == ROTATION_90 || orientation == ROTATION_270) {
            maxPreviewWidth = viewHeight;
            maxPreviewHeight = viewWidth;
        } else {
            maxPreviewHeight = viewHeight;
            maxPreviewWidth = viewWidth;
        }
        if (outputSizes != null) {
            Size max = null;
            for (Size it : outputSizes) {
                if (it.getWidth() < maxPreviewWidth && it.getHeight() < maxPreviewHeight) {
                    if (max == null) {
                        max = it;
                        continue;
                    }
                    if (max.getWidth() * max.getHeight() < it.getWidth() * it.getHeight()) {
                        max = it;
                    }
                }
            }
            if (max != null) {
                return max;
            }
        }

        return new Size(PREVIEW_WIDTH, PREVIEW_HEIGHT);
    }

    private class CameraDeviceCallback extends CameraDevice.StateCallback {
        @Override
        public void onOpened(CameraDevice camera) {
            cameraInstance = camera;
            startCaptureSession();
        }

        @Override
        public void onDisconnected(CameraDevice camera) {
            camera.close();
            cameraInstance = null;
        }

        @Override
        public void onError(CameraDevice camera, int error) {
            camera.close();
            cameraInstance = null;
        }
    }

    private class CaptureStateCallback extends CameraCaptureSession.StateCallback {
        @Override
        public void onConfigureFailed(CameraCaptureSession session) {
            Log.e(TAG, "Failed to configure capture session.");
        }

        @Override
        public void onConfigured(CameraCaptureSession session) {
            if (cameraInstance == null) {
                return;
            }
            captureSession = session;
            CaptureRequest.Builder builder = null;
            try {
                builder = cameraInstance.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
            if (surface != null) {
                if (builder != null) {
                    builder.addTarget(surface);
                }
            }
            try {
                if (builder != null) {
                    session.setRepeatingRequest(builder.build(), null, null);
                }
            } catch (CameraAccessException e) {
                Log.e(TAG, "Failed to start camera preview because it couldn't access camera", e);
            } catch (IllegalStateException e) {
                Log.e(TAG, "Failed to start camera preview.", e);
            }
            Size size = chooseOptimalSize();
            if (onCameraSizeChange != null) {
                onCameraSizeChange.onCameraSizeChange(size.getWidth(), size.getHeight());
            }
        }
    }
}
